﻿using System;
using System.IO;
using System.Collections.Generic;

namespace Game.Lib.CSG
{
    public class AABB : Shape3D
    {
        public override System.Collections.Generic.List<Vector> CriticalPoints
        {
            get
            {
                List<Vector> ret = new List<Vector>(9);

                ret.Add(Pos);

                ret.Add(Min);
                ret.Add(Min + new Vector3(Size.X, 0f, 0f));
                ret.Add(Min + new Vector3(0f, Size.Y, 0f));
                ret.Add(Min + new Vector3(Size.X, Size.Y, 0f));

                ret.Add(Max);
                ret.Add(Max - new Vector3(Size.X, 0f, 0f));
                ret.Add(Max - new Vector3(0f, Size.Y, 0f));
                ret.Add(Max - new Vector3(Size.X, Size.Y, 0f));

                return ret;
            }
        }
        public override Quaternion Rot
        {
            get
            {
                return Quaternion.Zero;
            }
            set
            {
                throw new InvalidOperationException();
            }
        }

        public Vector3 Min
        {
            get
            {
                return Pos - Size * .5f;
            }
        }

        public Vector3 Max
        {
            get
            {
                return Pos + Size * .5f;
            }
        }

        public override float SurfaceArea
        {
            get { return 3f * (Size.X * Size.Y + Size.Y * Size.Z + Size.X * Size.Z); }
        }

        public override float Volume
        {
            get { return Size.X * Size.Y * Size.Z; }
        }

        public AABB()
        {
            Pos = Size = Vector3.Zero;
        }

        public AABB(Vector3 pos, Vector3 size)
        {
            Pos = pos;
            Size = size;
        }

        public override AABB GetBoundingBox()
        {
            return this;
        }

        public override Ellipsoid GetBoundingSphere()
        {
            return new Ellipsoid(Pos, Size.Magnitude() * .5f);
        }

        public override bool Inside(Vector3 pos)
        {
            return Utils.Inside(pos.X, Min.X, Max.X) &&
                Utils.Inside(pos.Y, Min.Y, Max.Y) &&
                Utils.Inside(pos.Z, Min.Z, Max.Z);
        }

        public override Intersection RayCast(Ray r)
        {
            Plane[] data = new Plane[]{
                new Plane(Pos + new Vector3(Size.X * .5f, 0f, 0f), Vector3.XAxis),
                new Plane(Pos + new Vector3(Size.X * -.5f, 0f, 0f), Vector3.NegXAxis),
                new Plane(Pos + new Vector3(0f, Size.Y * .5f, 0f), Vector3.YAxis),
                new Plane(Pos + new Vector3(0f, Size.Y * -.5f, 0f), Vector3.NegYAxis),
                new Plane(Pos + new Vector3(0f, 0f, Size.Z * .5f), Vector3.ZAxis),
                new Plane(Pos + new Vector3(0f, 0f, Size.Z * -.5f), Vector3.NegZAxis) };
            float dist = float.PositiveInfinity;
            Intersection ret = Intersection.Missed;
            foreach (Plane p in data)
            {
                Intersection i = p.RayCast(r);
                if (i.Distance < dist)
                {
                    dist = i.Distance;
                    ret = i;
                }
            }
            return ret;
        }

        /// <summary>
        /// Effecient code for determining if a ray collides with this AABB
        /// </summary>
        /// <param name="r">The ray involved in the intersection</param>
        /// <returns></returns>
        public bool Intersects(Ray r)
        {
            float div = 1f / r.Direction.X;
            float tmin = (Min.X - r.Start.X) * div;
            float tmax = (Max.X - r.Start.X) * div;
            if (div < 0f) swap<float>(ref tmin, ref tmax);

            div = 1f / r.Direction.Y;
            float tymin = (Min.Y - r.Start.Y) * div;
            float tymax = (Max.Y - r.Start.Y) * div;
            if (div < 0f) swap<float>(ref tymin, ref tymax);

            if (tmin > tymax || tymin > tmax) return true;
            if (tymin > tmin) tmin = tymin;
            if (tymax < tmax) tmax = tymax;

            div = 1f / r.Direction.Z;
            float tzmin = (Min.Z - r.Start.Z) * div;
            float tzmax = (Max.Z - r.Start.Z) * div;
            if (div < 0f) swap<float>(ref tzmin, ref tzmax);

            if (tmin > tzmax || tzmin > tmax) return false;
            if (tzmin > tmin) tmin = tzmin;
            if (tzmax < tmax) tmax = tzmax;

            return tmax < r.Length;
        }

        private void swap<T>(ref T a, ref T b)
        {
            T t = a;
            b = a;
            a = t;
        }

        public override void Serialize(Stream s)
        {
            throw new System.NotImplementedException();
        }

        public override void Deserialize(Stream s)
        {
            throw new System.NotImplementedException();
        }
    }
}
